package com.xianzhi.dao.interceptor;


import com.xianzhi.bean.BaseBean;
import com.xianzhi.util.Page;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;

/**
 * 实现分页功能的自定义拦截器（插件）
 * 每次客户向服务器发送请求，只要符合规则就会执行相应的拦截器(Interceptor)
 * 拦截器的底层的实现方式就是动态代理模式（proxy）
 * 要实现自定义的拦截器就要实现mybatis中的Interceptor这个接口
 * 注解的说明：
 * 1.@Intercepts它表示当前的类是一个拦截器
 * 2.@Signature它标记了当前的拦截器只拦截StatementHandler接口中的prepare方法，这个方法的参数的类型为Connection类型的。
 */
@Intercepts({ @Signature(type = StatementHandler.class,method = "prepare",args = {Connection.class})})
public class PageInterceptor implements Interceptor {
  @Override
  public Object intercept(Invocation invocation) throws Throwable {
      //获取一个目标对象
      StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
        //获取到元数据对象,之后就可以从这个对象中可以通过其跟 方法来获取到我们要处理(拦截)的操作
        MetaObject metaObject = MetaObject.forObject(statementHandler,
          SystemMetaObject.DEFAULT_OBJECT_FACTORY,
          SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
          new DefaultReflectorFactory());
        //根据key(delegate.mappedStatement)获取到映射对象MappedStatement所对应的AdDao.xml
        MappedStatement mappedStatement = (MappedStatement)metaObject.getValue("delegate.mappedStatement");
        //获取到要拦截操作的ID
        String id = mappedStatement.getId();
        if(id.endsWith("ByPage")){
          BoundSql boundSql = statementHandler.getBoundSql();
          //获取到映射文件中的SQL
          String sql = boundSql.getSql();
          //计算总的记录数
          String countSql = "select count(*) from(" + sql + ")t";
          //可以通过Invocation中的方法获取到java.sql.Connection这个接口
          Connection conn = (Connection) invocation.getArgs()[0];
          PreparedStatement preparedStatement =  conn.prepareStatement(countSql);
          //要通过代理的方式来获取到ParameterHandler对象,目的就是要将?替换为具体的值
          ParameterHandler parameterHandler = (ParameterHandler)metaObject.getValue("delegate.parameterHandler");
          parameterHandler.setParameters(preparedStatement);
          //执行jdbc中的查询
          ResultSet rs = preparedStatement.executeQuery();
          //获取到BaseBean对象
          BaseBean bean = (BaseBean) boundSql.getParameterObject();
          Page page  =bean.getPage();
          if(rs.next()){
            page.setTotalNumber(rs.getInt(1));
          }
          //select id,title,img_file_name,link,weight from ad
          //通用的SQL:  select * from table limit(start-1)*limit,limit
          String pageSql = sql + " limit "+(page.getCurrentPage()-1)*page.getPageNumber()+","+page.getPageNumber();
          //重写分页SQL
          metaObject.setValue("delegate.boundSql.sql",pageSql);
        }

      //将执行权交给下一个拦截器，如果没有其它的拦截器啊，那么就会给调用controller来处理客户的请求
      return invocation.proceed();
  }

  @Override
  public Object plugin(Object target) {
    return Plugin.wrap(target,this);
  }

  @Override
  public void setProperties(Properties properties) {

  }
}
